build your own react
コンセプトは理解できるが、コードとして落とし込まれるとどんどん難解になってくるmiyamonz.icon
step 2まで
code:js
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) =>
typeof child === "object" ? child : createTextElement(child)
),
},
};
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
function render(element, container) {
// TODO
const dom =
element.type == "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type);
const isProperty = (key) => key !== "children";
Object.keys(element.props)
.filter(isProperty)
.forEach((name) => {
});
element.props.children.forEach((child) => render(child, dom));
container.appendChild(dom);
}
const Didact = {
createElement,
render,
};
/** @jsx Didact.createElement */
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
);
const container = document.querySelector("#app");
render(element, container);
愚直な実装
すべてをそのままレンダリングしてる
step3 concurrent mode
ここでもうconcurrent modeの話になる
再帰でレンダリングを実装したため、途中で止められない
それを途切れるようにする
こういう感じでやる
code:js
let nextUnitOfWork = null
function workLoop(deadline) {
let shouldYield = false
while(nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
shoudYield = deadline.timeRemaining() < 1
}
requestIdleCallback(workLoop)
}
requestIdleCallback(workLoop)
function performUnitOfWork(nextUnitOfWork) {
// TODO
}
優先度の低い処理をブラウザのアイドル時に実行させる
つまりworkLoopのdeadlineはこのapiで得られるもの
step4 fiber
そして、木構造を順番に処理しつつ、かつ途中で止められるようなことを考える
code:js
Didact.render(
<div>
<h1>
<p />
<a />
</h1>
<h2 />
</div>,
container
)
個別のelementをfiberという仕事の単位として扱う
jjjkkk
https://gyazo.com/df2506393ce6609e838845c62fe28c8d
renderないでやりたいこと
elementをDOMに追加
elementの雇用そのためにfiberを作成
next unit of workを選択
fiberでの作業完了後
子要素がいれば子要素へ
子要素が居なければ兄弟へ
子も兄弟もいなければ、自分の親の兄弟へ
親も兄弟が居なければ、さらに親に戻る
perform unit of workでこれを実装
nextUnitOfWorkにfiberを突っ込む
code:js
function performUnitOfWork(fiber) {
// add dom node
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom);
}
// create new fibers
const elements = fiber.props.children;
let index = 0;
let prevSibling = null;
while (index < elements.length) {
const element = elementsindex; const newFiber = {
type: element.type,
props: element.props,
parent: fiber,
dom: null,
};
if (index === 0) {
fiber.child = newFiber;
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
index++;
}
//return next unit of work
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) return nextFiber.sibling;
nextFiber = nextFiber.parent;
}
}
step 5
上の関数でのここ
code:js
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom);
}
親がいたらそのdomに追加、としている
こんままだとUIが変になる
ブラウザは我々がツリー全体をレンダリングする前にinterruptしうる
中途半端な状態で割り込まれると、中途半端な状態が見える?
仮想DOMの処理途中で実DOM反映をしてしまっている
この処理をperformUnitOfworkじゃないとこでやる
workLoop内で次のタスクがない状態でwipRootがあるときに、実domに追加する
先に仮想DOMの処理をして、やることがなくなってからrenderするということか
このstep5だと、commit部分は一気にやるんだな
step 6
リコンサイラ
やっと差分のみ更新だぞ!
最後まで見た
配列サポートしてないが、flat挟めば動く
keyは考慮してない